package com.loool.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

public class ExcelOuter<Objcet> {
	// 常量，基础输出路径
	static final String BASE_PATH = "";
	// 常量，excel一个sheet最多的行数
	static final int MAX_ROW = 65530;

	// 保存excel workbook对象
	private HSSFWorkbook m_book = null;
	// 保存excel worksheet对象
	private HSSFSheet m_sheet = null;
	// 用户设定文件名
	private String m_file = null;
	// 文件输出流
	private OutputStream m_streamOut = null;
	// excel cells除表头标题外整体风格
	private HSSFCellStyle m_style = null;
	// excel cells表头标题风格
	private HSSFCellStyle m_title_style = null;
	// 当前在excel中输出的行数
	private int m_cIndex;
	// 实际输出文件地址
	private String m_realPath = null;
	// 当前在excel中输出的sheet页序列数
	private int m_sIndex;
	// 保存的excel表头字段，用于数据太多新建表的时候使用
	private List<String> m_keys = null;
	// 输出
	private ServletOutputStream outputStream;

	/**
	 * (创建excel文件对象)
	 * @param String
	 *            fileName 文件名，传全路径
	 * @param String
	 *            sheetName 输出excel sheet的名称，传“”时为数据导出
	 * @return boolean 是否成功
	 */
	public boolean ceateExcel(String fileName, String sheetName,
			boolean columnWidth) {
		if (fileName.isEmpty())
			return false;
		try {
			m_cIndex = 1;
			m_sIndex = 0;
			m_book = new HSSFWorkbook();
			m_sheet = m_book.createSheet(sheetName.isEmpty() ? "数据导出"
					: sheetName);
			if (columnWidth) {
				m_sheet.setColumnWidth(1, 12 * 2800);
			}
			m_file = fileName;
			m_realPath = BASE_PATH + m_file;
			m_realPath = m_realPath.replace("//", "/");
			m_streamOut = new FileOutputStream(m_realPath);
			m_style = m_book.createCellStyle();
			m_style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
			return true;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * (完成对excel的编辑，并输出文件)
	 * @return String 返回文件路径 - 之前输入的路径
	 */
	public String endEdit() {
		try {
			m_book.write(m_streamOut);
			m_streamOut.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return m_realPath;
	}

	/**
	 * (添加excel表头)
	 * @param List
	 *            <String> keys 定义excel表头数据，注意需要跟后面传输的数据类里面属性顺序一致
	 * @return boolean 是否成功
	 */
	public boolean addHead(List<String> keys) {
		m_keys = keys;
		HSSFRow row = m_sheet.createRow(0);
		int index = 0;
		HSSFFont headfont = m_book.createFont();
		headfont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);// 加粗
		m_title_style = m_book.createCellStyle();
		m_title_style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
		m_title_style.setFont(headfont);
		for (String key : keys) {
			row.createCell(index).setCellValue(key);
			row.getCell(index++).setCellStyle(m_title_style);
		}
		return true;
	}

	/**
	 * (当sheet页超过最大限制时，创建新sheet页)
	 * @return void
	 */
	private void createNewSheet() {
		String sName = m_sheet.getSheetName();
		sName = String.format("%s_%d", sName, ++m_sIndex);
		m_sheet = m_book.createSheet(sName);
		m_cIndex = 1;
		addHead(m_keys);
	}

	/**
	 * (添加一项数据)
	 * @param T
	 *            obj 保存项目数据的对象
	 * @return void 通过异常返回错误
	 * @throws IllegalAccessException
	 */
	@SuppressWarnings("rawtypes")
	private <T> void addItem(T obj) throws IllegalArgumentException,
			ReflectiveOperationException, IllegalAccessException {
		Class c = obj.getClass();
		Field[] fieldArray = c.getFields();
		int index = 0;
		if (m_cIndex >= MAX_ROW)
			createNewSheet();
		HSSFRow row = m_sheet.createRow(m_cIndex++);
		for (Field field : fieldArray) {
			row.createCell(index).setCellValue(field.get(obj).toString());
			row.getCell(index++).setCellStyle(m_style);
		}
	}

	/**
	 * (添加一批数据)
	 * @param List
	 *            <T> objs 数据列表，注意T类型的属性，要跟之前设定表头时候保持一致
	 * @return boolean 是否成功
	 */
	public <T> boolean addObjs(List<T> objs) throws IOException {
		try {
			try {
				for (T obj : objs)
					addItem(obj);
				return true;
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (ReflectiveOperationException e) {
				e.printStackTrace();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * (添加一像数据)
	 * @param T
	 *            objs 一项数据，注意T类型的属性，要跟之前设定表头时候保持一致
	 * @return boolean 是否成功
	 */
	public <T> boolean addObj(T obj) throws IOException {
		try {
			addItem(obj);
			return true;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (ReflectiveOperationException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * (添加一项数据)
	 * @param List
	 *            <Objcet> datas 数据列表
	 * @return boolean 添加是否成功
	 */
	private boolean addList(List<Objcet> datas) {
		if (datas == null)
			return false;
		int index = 0;
		if (m_cIndex >= MAX_ROW)
			createNewSheet();
		HSSFRow row = m_sheet.createRow(m_cIndex++);
		for (Object data : datas) {
			row.createCell(index).setCellValue(
					data == null ? "" : data.toString());
			row.getCell(index++).setCellStyle(m_style);
		}
		return true;
	}

	/**
	 * (添加一项数据)
	 * @param List
	 *            <Objcet> datas 数据列表
	 * @return int 标识添加成功条数，返回0表示添加失败
	 */
	public int add(List<List<Objcet>> objs) {
		if (objs == null)
			return 0;
		int ret = 0;
		for (List<Objcet> obj : objs) {
			if (addList(obj) == true)
				ret++;
		}
		return ret;
	}

	/**
	 * 生成xls文件
	 * 
	 * @author huyaohui
	 * @date 2017-11-9
	 * @param headList
	 *            表头
	 * @param objList
	 *            正文内容
	 * @param fileName
	 *            表格名称（绝对路径）
	 * @param sheetName表格sheet名称
	 * @return
	 */
	public static boolean excel(List<String> headList,
			List<List<Object>> objList, String fileName, String sheetName) {
		ExcelOuter<Object> ehExcel = new ExcelOuter<Object>();
		try {
			ehExcel.ceateExcel(fileName, sheetName, false);
			ehExcel.addHead(headList);
			ehExcel.add(objList);
			ehExcel.endEdit();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 创建Excel对象
	 * 直接输出到流中
	 * @param servletRequest
	 * @param servletResponse
	 * @param headList
	 * @param objList
	 * @param fileName
	 * @param sheetName
	 * @return
	 */
	public static boolean excel(HttpServletRequest request,
			HttpServletResponse response, List<String> headList,
			List<List<Object>> objList, String fileName, String sheetName) {
		ExcelOuter<Object> ehExcel = new ExcelOuter<Object>();
		try {
			ehExcel.ceateExcel(fileName, sheetName, false);
			ehExcel.addOutputStream(request, response);
			ehExcel.addHead(headList);
			ehExcel.add(objList);
			ehExcel.exportExcel();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 创建输出流
	 * 
	 * @param servletRequest
	 * @param servletResponse
	 * @throws IOException
	 */
	public void addOutputStream(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		if (request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") > 0) {
			String fileName = URLEncoder.encode(m_file, "UTF-8");
			if (fileName.length() > 150)// 解决IE 6.0 bug
			{
				fileName = new String(m_file.getBytes("GBK"), "ISO-8859-1");
				m_file = fileName;
			} else {
				m_file = fileName;
			}
		} else {
			m_file = new String(m_file.getBytes("GBK"), "ISO8859-1");
		}
		response.reset();
		response.setContentType("application/vnd.ms-excel;charset=utf-8");
		response.setHeader("Content-disposition", "attachment; filename="
				+ m_file);
		outputStream = response.getOutputStream();
	}

	/**
	 * 向浏览器响应数据
	 * 
	 * @return
	 */
	public String exportExcel() {
		try {
			m_book.write(outputStream);
			outputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return m_realPath;
	}
}
